<?php 
defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * 消息模型
 * 
 * @desc	消息模型，发送消息，媒介+渠道/通道。
 * 			(WEB APP SMS EML)*(0 1 2...)
 * @version	1.0.0 
 * @author	Jason
 * @date	2017年7月18日 下午4:58:17
 */
class Mod_message extends MY_Model
{
	private $station_id = 0;//tag
	private $feature_id = 0;//tag_and
	private $regist_id = 0;//registration_id
	private $source_id  = 0;
	private $target_id  = 0;
	private $extra      = '';//消息额外字段信息
	/** @var libraries\common\MessagePusherInterface */
	private $pusher     = null;
	
	protected $table_mapper = [
			'master' => 'message',
			'template'   => 'message_template',
			'container'   => 'message_container',
	];
	
	protected $allowed_fields = [
			// MARK 消息发送媒介冗余字段：双表系统是可以不冗余该字段的。但是三表比较麻烦：IN局限，联表慢，故冗余。
			'master' => ['source_id', 'target_id', 'template_id', 'template_medium', 'content', 'delay_duration', 'send_time', 'extra', 'status'],
			'template' => ['name', 'code', 'model', 'oper', 'value', 'delay_duration', 'other', 'medium', 'channel', 'extra'],
			// MARK 消息发送媒介冗余字段：双表系统是可以不冗余该字段的。但是三表比较麻烦：IN局限，联表慢，故冗余。
			'container' => ['source_id', 'target_id', 'template_id', 'template_medium', 'message_id', 'receive_time', 'status'],
	];
	
	use models\common\BinaryBitBasedStatus;
	
	public function __construct()
	{
		parent::__construct();
		
		$this->load->database();
		
		$this->config->load('message');
		
		$this->status_bit_config = [
				'master' => [
						// 发送方状态：是否保留（存档、不删除），肯定已读取！
						'status_bit_length' => 1,
						'status_bit_positive_tags' => ['retained'],
						'status_bit_negative_tags' => ['deleted'],
				],
				'template' => [
				],
				'container' => [
						// 接收方状态：是否读取、是否保留（存档、不删除）
						'status_bit_length' => 2,
						'status_bit_positive_tags' => ['read', 'retained'],
						'status_bit_negative_tags' => ['unread', 'deleted'],
				],
		];
	}
	
	/**
	 * 插入前处理器
	 *
	 * @desc  主要负责格式化一些字段
	 * @param array  $data
	 * @param string $mapping	表映射
	 *
	 * @return array
	 */
	public function before_create_data_handler($data, $mapping = 'master')
	{
		if ($mapping == 'master') {
			if (!empty($data['extra'])) {
				$data['extra'] = is_array($data['extra']) ? serialize($data['extra']) : $data['extra'];
			}
			$data['status'] = $this->calculate_status_by_tags(['retained'], 'positive', $mapping);//发送方未删
		}
		if ($mapping == 'template') {
			if (!empty($data['extra'])) {
				$data['extra'] = is_array($data['extra']) ? serialize($data['extra']) : $data['extra'];
			}
		}
		if ($mapping == 'container') {
			$data['status'] = $this->calculate_status_by_tags(['retained'], 'positive', $mapping);//接收方未删，未读
		}
		
		return $data;
	}
	
	/**
	 * 更新前处理器
	 *
	 * @desc  主要负责格式化一些字段 & 过滤掉多语言字段，确保只在默认语言下更改主表的多语言字段
	 * @param number $id
	 * @param array  $data
	 * @param string $mapping	表映射
	 *
	 * @return array
	 */
	public function before_update_data_handler($id, $data, $mapping = 'master')
	{
		// TODO 不更新代码
// 		$data = $this->before_create_data_handler($data, $mapping);
		
		return $data;
	}
	
	/**
	 * 根据从数据库元数据处理计算消息内容，并添加到数组返回
	 *
	 * @param string $formatMessage 元数据数组
	 *
	 * @return array 反回的数组中title为模板名称，content为消息内容
	 */
	public function formatMessageContent($formatMessage)
	{
		if(!empty($formatMessage['content'])) {
			$params = unserialize($formatMessage['content']);
			extract($params);
		}
		
		$messageTemplate = $this->select_instance_by_id($formatMessage['template_id'], 'template');
		if(empty($messageTemplate)) return $formatMessage;
		
		$formatMessage['title']      = eval(sprintf('return "%s";', $messageTemplate['name']));
		$formatMessage['content']    = eval(sprintf('return "%s";', $messageTemplate['model']));
		
		return $formatMessage;
	}
	
	/**
	 * 添加消息主表数据及发送消息的执行操作 - 仅后台管理员手动添加消息时用
	 *
	 * @param array $data 消息数组
	 * @param array $map
	 *
	 * @return string(success or error)
	 */
	public function sendMessagePerform($data)
	{
		$customContent = $data['customContent'];
		
		//消息发送媒介：站内信、APP推送、手机短信还是电子邮件。
		$medium  = $data['medium'];
		if($medium == 'WEB') {
			$response = $this->sendWebMessage($customContent, $data['source_id'], $data['target_id']);
		}
		else if($medium == 'APP') {
			$response = $this->sendAppMessage($customContent, $data['source_id'], $data['target_id'], @$data['station_id'], @$data['feature_id'], @$data['regist_id']);
		}
		else if($medium == 'SMS') {
			$response = $this->sendSmsMessage($data['mobile'], $customContent, $data['source_id'], 0);
		}
		else if($medium == 'EML') {
			$response = $this->sendEmlMessage($data['email'], $data['subject'], $customContent, $data['source_id'], 0);
		}
		
		return $response;
	}
	
	/**
	 * Send into Message Poll Perform
	 * 
	 * @desc    and Push Sometimes.
	 * @author  Jason
	 * @date    2017年7月
	 * 
	 * @param   type variable
	 * 
	 * @return  array	返回一个数组：有总操作状态和错误类型和信息。
	 * 	        eg:	array($sendStatus && $pushStatus, [$errorTypes, $dbErrors, $pushErrors])
	 * 				array(true, [null, null, null])
	 * 				array(false, array(['db_error', 'sp_error'], ['Error: ErrorMessage'], ['cannot find user by this audience', 'The registration_id u'registration_3' is invalid!']]))
	 */
	private function sendPerform($function, $params)
	{
		// 根据CODE获得消息模板
		$code = $function;
		$template = $this->select_instance_by_conditions([['where', "`code`='{$code}'"]], 'template');
		
		if (!empty($template)) {
			$nowTime = time();
			$sendTime = ($template['delay_duration'] > 0) ? 0 : $nowTime;
			
			$this->extra = array(
					'station_id' => $this->station_id,
					'feature_id' => $this->feature_id,
					'regist_id' => $this->regist_id,
			);
			
			$data = array(
					'source_id'     => $this->source_id,
					'target_id'     => $this->target_id,
					'template_id'   => $template['id'],
					// MARK 消息发送媒介冗余字段：双表系统是可以不冗余该字段的。但是三表比较麻烦：IN局限，联表慢，故冗余。
					'template_medium'   => $template['medium'],
					'content'       => serialize($params),
					'create_time'   => $nowTime,
					'delay_duration'=> $template['delay_duration'],
					'send_time'     => $sendTime,
					'extra'         => serialize($this->extra),
			);
			$errorTypes = [];
			$sendStatus = true; $dbErrors = [];
			$datas = self::breakUpData($data);
			foreach ($datas as $data) {
				list($status, $content) = $this->create_instance($data);
				$sendStatus = $sendStatus && $status;
				if (!$status) {
					$dbErrors[] = $content['errors'];
				}
			}
			$sendStatus OR $errorTypes[] = 'db_error';
			
			$pushStatus = true; $pushErrors = [];
			if ($template['delay_duration'] == 0) {
				//同步
				$pushStatus = $this->pushPerform($template, $params);
				if (!$pushStatus) {
					$pushErrors[] = $this->getErrorMessage();
				}
			} else if (in_array($template['medium'], ['APP', 'SMS', 'EML'])) {
				// TODO 处理延时发送：插入队列.then.Push
			}
			$pushStatus OR $errorTypes[] = 'sp_error';
			
			$status = $sendStatus && $pushStatus;
			if (!$status) {
				$content = [$errorTypes, $dbErrors, $pushErrors];
			} else {
				$content = [null, null, null];
			}
			
			return [$status, $content];
		}
		
		$content = [['message_msg_template_notfound'], null, null];
		return [false, $content];
	}
	
	private function pushPerform($messageTemplate, $params)
	{
		foreach ($params as $key => $value) {
			$$key = $value;
		}
		eval("\$format = \"" . $messageTemplate['model'] . "\";");
		
		$data = array(
				'sid'   => $this->station_id,
				'fid'   => $this->feature_id,
				'rid'   => $this->regist_id,
				'uid'   => $this->target_id,
				'msg'   => $format,
				'oper'  => $messageTemplate['oper'],
				'value' => $messageTemplate['value'],
				'other' => $messageTemplate['other'],
				'extra' => $messageTemplate['extra'],
		);
		
		if($messageTemplate['medium'] == 'WEB') {
			//nothing to do here! because this channel needn't send!
		}
		else if($messageTemplate['medium'] == 'APP') {
			//nothing already
		}
		else if($messageTemplate['medium'] == 'SMS') {
			$data['mobile'] = $mobile;
		}
		else if($messageTemplate['medium'] == 'EML') {
			$data['email'] = $email;
			$data['subject'] = $subject;
		}
		
		if (in_array($messageTemplate['medium'], ['WEB', 'APP', 'SMS', 'EML'])) {
// 			$pushProvider = $this->config->item('ServiceProvider')['appConfig']['appPSP'];
// 			$appConfig = $this->config->item('ServiceProvider')['appConfig'];
// 			//if($pushProvider != 'JPush'){ break; }
// 			$channelKey = $messageTemplate['medium'] . $messageTemplate['channel'];
// 			$channelConfig = $this->config->item('ServiceProvider')['appConfig'][$pushProvider][$channelKey];
// 			$pusherParams = [
// 					'pushProvider' => $pushProvider,
// 					'channelConfig' => $channelConfig,
// 			];
// 			$this->load->driver('MessagePusherApp', $pusherParams);
// 			$this->pusher = $this->MessagePusherApp;
// 			$this->pusher->pushPerform($data);
// 			//可以调用$this->getSendStatus来取操作状态
			// MARK 为了统一抽象编码，枚举关键字尽量集中，少。故 appPSP smsMSP emlMsp ~-> MPSP；APP1 SMS1 EML1 ~-> CH1；NoChannel ->~ CH0。
			$medium = $messageTemplate['medium']; // APP
			$mediumKey = strtolower($medium) . 'Config'; // appConfig
			$mediumConfig = $this->config->item('ServiceProvider')[$mediumKey];
			$pushProvider = $mediumConfig['MPSP'];
			//if($pushProvider != 'JPush'){ break; }
			$channelKey = "CH" . $messageTemplate['channel'];
			$channelConfig = $mediumConfig[$pushProvider][$channelKey];
			$pusherParams = [
					'pushProvider' => $pushProvider,
					'channelConfig' => $channelConfig,
			];
			$libraryKey = 'MessagePusher' . ucfirst(strtolower($medium)); // MessagePusherApp
			$this->load->driver($libraryKey, $pusherParams);
			#MARK CI 这些东西全小写！！
			#$this->pusher = $this->$libraryKey;
			$this->pusher = $this->{strtolower($libraryKey)};
			$this->pusher->pushPerform($data);
			//可以调用$this->getSendStatus来取操作状态
			$status = $this->getSendStatus();
			//如果错误可以调用$this->getErrorMessage来取错误信息
			//$message = $this->getErrorMessage();
			return $status;
		}
		// 媒介不被支持，执行成功了啊，OK！
		return true;
	}
	
	public function getSendStatus()
	{
		if(empty($this->pusher)){
			//trigger_error("MessageModel::getSendStatus: You Shouldn't call me here, because no pusher instance now!", E_USER_ERROR);
			return true;
		}
		return $this->pusher->getStatus();
	}
	public function getErrorMessage()
	{
		if(empty($this->pusher)){
			//trigger_error("MessageModel::getSendStatus: You Shouldn't call me here, because no pusher instance now!", E_USER_ERROR);
			return '';
		}
		return $this->pusher->getMessage();
	}
	
	/**
	 * 将第二维的数组打散, 形成全一维的多条记录
	 *
	 * @param array $insertArray
	 *
	 * @return array
	 * eg:
	 * input:   array('a', (1,2,3))
	 * output:  array(array(a, 1), array(a, 2), array(a,3))
	 */
	private static function breakUpData($insertArray)
	{
		if(!is_array($insertArray['target_id'])){
			$insertArray['target_id'] = array($insertArray['target_id']);
		}
		
		$retData = array();
		foreach ($insertArray['target_id'] as $value) {
			$tmpData = $insertArray;
			$tmpData['target_id'] = $value;
			$retData[] = $tmpData;
		}
		
		return $retData;
	}
	
	
	
	
	
	
	
	
	
	
	/************************************以下是各种模板消息码函数**************************************/
	
	// TODO move out!
	
	public function systemMessage($customContent, $source_id, $target_id)
	{
		$this->source_id = $source_id;
		$this->target_id = $target_id;
		$data = array('customContent'=>$customContent);
		return $this->sendPerform(__FUNCTION__, $data);
	}
	
	public function sendSmsCaptcha($mobile, $captcha, $source_id = 0, $target_id = 0)
	{
		$this->source_id = $source_id;
		$this->target_id = $target_id;
		$data = array('mobile'=>$mobile, 'captcha'=>$captcha);
		return $this->sendPerform(__FUNCTION__, $data);
	}
	
	public function sendWebMessage($customContent, $source_id, $target_id)
	{
		$this->source_id = $source_id;
		$this->target_id = $target_id;
		$data = array('customContent'=>$customContent);
		return $this->sendPerform(__FUNCTION__, $data);
	}
	
	public function sendAppMessage($customContent, $source_id, $target_id, $station_id, $feature_id, $regist_id)
	{
		$this->source_id = $source_id;
		$this->target_id = $target_id;
		$this->station_id = $station_id;
		$this->feature_id = $feature_id;
		$this->regist_id = $regist_id;
		$data = array('customContent'=>$customContent);
		return $this->sendPerform(__FUNCTION__, $data);
	}
	
	public function sendSmsMessage($mobile, $customContent, $source_id, $target_id)
	{
		$this->source_id = $source_id;
		$this->target_id = $target_id;
		$data = array('mobile'=>$mobile, 'customContent'=>$customContent);
		return $this->sendPerform(__FUNCTION__, $data);
	}
	public function sendEmlMessage($email, $subject, $customContent, $source_id, $target_id)
	{
		$this->source_id = $source_id;
		$this->target_id = $target_id;
		$data = array('email'=>$email, 'subject'=>$subject, 'customContent'=>$customContent);
		return $this->sendPerform(__FUNCTION__, $data);
	}
	
	public function receiptArrivalSmsNotice($mobile, $orderId, $code, $source_id, $target_id)
	{
		$this->source_id = $source_id;
		$this->target_id = $target_id;
		$data = array('mobile'=>$mobile, 'orderId'=>$orderId, 'code' => $code);
		return $this->sendPerform(__FUNCTION__, $data);
	}
}